package bigNumbers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Number
{
	private static String SPACER = " ";
	private static int SPACER_DIGIT = 4;
	public static int FORWARD = 1;
	public static int BACKWARD = -1;
	public static Number ZERO = new Number("0");

	private String number = "";
	private ArrayList<Integer> digitis = new ArrayList<Integer>();
	private int pointPosition = 0;
	private int numberLenght = 0;
	private int decimalsLenght = 0;
	private int intereLenght = 0;
	private int sign = 1;
	private String name = "";

	public Number(String number)
	{
		this.number = number;
		calculateDigits();
	}

	public Number(String name, String number)
	{
		this.name = name;
		this.number = number;
		calculateDigits();
	}

	public Number(String name, List<Integer> numbersArray)
	{
		this.name = name;
		this.number = numbersArray.toString().replace(",", "").replace("[", "").replace("]", "").replace(" ", "");
		calculateDigits();
	}

	public Number()
	{
	};

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	private void calculateDigits()
	{
		if (number.startsWith("-"))
		{
			sign = -1;
			number = number.substring(1);
		}
		digitis = new ArrayList<Integer>();
		numberLenght = number.length();
		pointPosition = numberLenght;
		intereLenght = numberLenght;
		for (int i = 0; i < number.length(); i++)
		{
			String d = number.substring(i, i + 1);
			if (d.equalsIgnoreCase("."))
			{
				pointPosition = i - 1;
				intereLenght = i;
				numberLenght--;
				continue;
			}
			digitis.add(Integer.parseInt(d));
		}
		decimalsLenght = numberLenght - intereLenght;
	}

	public String getNumber()
	{
		return number;
	}

	public void setNumber(String number)
	{
		this.number = number;
		calculateDigits();
	}

	/**
	 * Give the position of the decimal Point
	 * 
	 * @return
	 */
	public int getPointPosition()
	{
		return pointPosition;
	}

	public int getNumberLenght()
	{
		return numberLenght;
	}

	public int getDecimalsLenght()
	{
		return decimalsLenght;
	}

	public Number align(Number n)
	{

		if (this.getDecimalsLenght() > n.getDecimalsLenght())
		{
			String zeros = getZeros(this.getDecimalsLenght() - n.getDecimalsLenght());
			String point="";
			if (n.getDecimalsLenght()==0)
				point=".";
			n.setNumber(n.getNumber() + point+zeros);
			
		}
		if (this.getDecimalsLenght() < n.getDecimalsLenght())
		{
			String zeros = getZeros(-this.getDecimalsLenght() + n.getDecimalsLenght());
			String point="";
			if (this.getDecimalsLenght()==0)
				point=".";
			this.setNumber(this.getNumber() + point+ zeros);
		}
		if (this.getIntereLenght() > n.getIntereLenght())
		{
			String zeros = getZeros(this.getIntereLenght() - n.getIntereLenght());
			n.setNumber(zeros + n.getNumber());

		}
		else if (this.getIntereLenght() < n.getIntereLenght())
		{
			String zeros = getZeros(-this.getIntereLenght() + n.getIntereLenght());
			this.setNumber(zeros + this.getNumber());

		}

		return n;
	}

	private String getZeros(int n)
	{
		if (n == 0)
			return "";
		StringBuffer res = new StringBuffer();
		for (int i = 0; i < n; i++)
		{
			res.append("0");
		}
		return res.toString();
	}

	@Override
	public String toString()
	{
		StringBuffer res = new StringBuffer(name);
		res.append((" : "));
		res.append((sign < 0 ? "-" : "+"));
		for (int index = 0; index < numberLenght; index++)
		{
			res.append(digitis.get(index));
			if (index == pointPosition)
				res.append(".");
			if (index != pointPosition && index % SPACER_DIGIT == 0)
				res.append(SPACER);
		}
		return res.toString();
	}

	public int getIntereLenght()
	{
		return intereLenght;
	}

	public void setIntereLenght(int intereLenght)
	{
		this.intereLenght = intereLenght;
	}

	public ArrayList<Integer> getDigitis()
	{
		return digitis;
	}

	public static Number add(Number aaa, Number bbb) throws Exception
	{
		if (aaa.isZero())
			return bbb;
		if (bbb.isZero())
			return aaa;
		aaa = bbb.align(aaa);
		// System.out.println(n.getDecimalsLenght());
		// System.out.println(this.getDecimalsLenght());
		int resto = 0;
		StringBuffer res = new StringBuffer();
		if (aaa.getSign() * bbb.getSign() == 1)
		{
			int sign = aaa.getSign();

			for (int i = bbb.getNumberLenght() - 1; i >= 0; i--)
			{
				// System.out.println(i + ") this: " + this.get(i) + " n: " +
				// n.get(i) + " resto: " + resto);
				int b = bbb.get(i);
				int a = aaa.get(i);
				int r = bbb.get(i) + aaa.get(i) + resto;
				resto = 0;
				if (r > 9)
				{
					r = r - 10;
					resto = 1;
				}
				res.append(r);

			}
			if (resto == 1)
				res.append(resto);

			res.insert(bbb.getDecimalsLenght(), ".");
			Number n = new Number(bbb.name + " add (" + aaa.name + ") ", res.reverse().toString());
			n.setSign(sign);
			return n;
		}
		else
		{
			if (aaa.getSign() == -1 && bbb.getSign() == 1)
			{
				aaa.setSign(1);
				int compare = isMinor(aaa, bbb);
				if (compare == 1)
				{
					Number n = subtract(aaa, bbb);
					n.setSign(-1);
					return n;
				}
				else if (compare == -1)
				{
					Number n = subtract(bbb, aaa);
					n.setSign(1);
					return n;
				}
		
					return ZERO;
				
			}
			else //if (aaa.getSign() == 1 && bbb.getSign() == -1)
			{
				bbb.setSign(1);
				int compare = isMinor(aaa,bbb);
				if (compare == 1)
				{
					Number n = subtract(aaa, bbb);
					return n;
				}
				else if (compare == -1)
				{
					Number n = subtract(bbb, aaa);
					n.setSign(-1);
					return n;
				}
				
					return ZERO;
				
			}
		}

	}

	private boolean isZero()
	{
		int t=0;
		for(int i : digitis){
			t=t+i;
		}
		if (t==0)
			return true;
		return false;
	}

	public int getSign()
	{
		return sign;
	}

	public void setSign(int sign)
	{
		this.sign = sign;
	}

	public int get(int i)
	{
		return digitis.get(i);
	}

	public static Number subtract(Number aaa, Number bbb) throws Exception
	{
		aaa = bbb.align(aaa);
		if (aaa.isZero())
			return bbb.changeSign();
		
		if (bbb.isZero())
			return aaa.changeSign();


		if(aaa.getSign()==-1 && bbb.getSign()==-1){
			
			int comp = isMinor(aaa,bbb);
			if(comp == 1){
				Number n = sub(bbb,aaa);
				n.setSign(1);
				return n;
			} else if(comp == -1){
				Number n = sub(aaa,bbb);
				n.setSign(-1);
				return n;
			}
			else{
				return ZERO;
			}
		} else if(aaa.getSign()==1 && bbb.getSign()==1){
			
			int comp = isMinor(aaa,bbb);
			if(comp == 1){
				Number n = sub(aaa,bbb);
				n.setSign(1);
				return n;
			} else if(comp == -1){
				Number n = sub(bbb,aaa);
				n.setSign(-1);
				return n;
			}
			else{
				return ZERO;
			}
			
		} else if(aaa.getSign()==-1 && bbb.getSign()==1){
			aaa.setSign(1);
			Number n = add(aaa,bbb);
			n.setSign(-1);
			return n;
		}else{

			bbb.setSign(1);
			Number n = add(aaa,bbb);
			return n;
		}
	}

	private Number changeSign()
	{
		this.sign=this.sign*-1;
		return this;
	}

	private static Number sub(Number aaa, Number bbb) throws Exception{

		int resto = 0;
		StringBuffer res = new StringBuffer();

		int sign = aaa.getSign();

		for (int i = bbb.getNumberLenght() - 1; i >= 0; i--)
		{
			// System.out.println(i + ") this: " + this.get(i) + " n: " +
			// n.get(i) + " resto: " + resto);
			int b = bbb.get(i);
			int a = aaa.get(i);
			int r = aaa.get(i) - bbb.get(i) + resto;
			resto = 0;
			if (r < 0)
			{
				r = r + 10;
				resto = -1;
			}
			res.append(r);

		}
		if (resto == -1)
			throw new Exception("IN Subtractions last rest cant be negative");

		res.insert(bbb.getDecimalsLenght(), ".");
		Number n = new Number(bbb.name + " add (" + aaa.name + ") ", res.reverse().toString());
		n.setSign(-1);
		return n;
	}
	/**
	 * compare two Numbers a & b a<b -> 1 a>b -> -1 a=b -> 0
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public static int isMinor(Number a, Number b)
	{
		a = a.clearZero();
		b = b.clearZero();
		if (a.getSign() < b.getSign())
		{
			return -1;
		}
		else if (a.getSign() > b.getSign())
		{
			return 1;
		}
		else
		{
			if (a.getSign() == 1)
			{
				if (a.getIntereLenght() < b.getIntereLenght())
				{
					return -1;
				}
				else if (a.getIntereLenght() > b.getIntereLenght())
				{
					return 1;
				}
				else
				{
					for (int i = 0; i < a.numberLenght; i++)
					{
						if (i >= b.getNumberLenght())
							return 1;
						if (a.get(i) < b.get(i))
						{
							return -1;
						}
						else if (a.get(i) > b.get(i))
						{
							return 1;
						}
					}
				}
				return 0;
			}
			else
			{
				if (a.getIntereLenght() < b.getIntereLenght())
				{
					return 1;
				}
				else if (a.getIntereLenght() > b.getIntereLenght())
				{
					return -1;
				}
				else
				{
					for (int i = 0; i < a.numberLenght; i++)
					{
						if (i >= b.getNumberLenght())
							return -1;
						if (a.get(i) < b.get(i))
						{
							return 1;
						}
						else if (a.get(i) > b.get(i))
						{
							return -1;
						}
					}
				}
				return 0;
			}

		}

	}

	public Number clearZero()
	{
		String res = "";
		int zeros = 0;
		for (int i = 0; i < this.intereLenght; i++)
		{
			if (this.get(i) != 0)
			{
				zeros = i;
				break;
			}
		}
		res = this.getNumber().substring(zeros);
		if (numberLenght != pointPosition)
		{
			for (int i = res.length(); i > 0; i = i - 1)
			{
				if (!res.substring(i - 1, i).equalsIgnoreCase("0"))
				{
					zeros = i;
					break;
				}
			}
			res = res.substring(0, zeros);
		}
		Number n = new Number(this.name, res);
		n.setSign(this.sign);
		return n;
	}
	
	public Number multiply10(int times){
		for(int i=0;i<times;i++){
			digitis.add(0);			
		}
		numberLenght=numberLenght+times;
		decimalsLenght=decimalsLenght-times;
		if(decimalsLenght<0)
			decimalsLenght=0;
		intereLenght=intereLenght+times;
		pointPosition=pointPosition+times;
		if(pointPosition>numberLenght)
			pointPosition=numberLenght;
		name="10^"+times+"("+name+")";
		return this;
	}
	
	public static  ArrayList<Integer> multiply10(ArrayList<Integer> array, int times){
		for(int i=0;i<times;i++){
			array.add(0);			
		}
		return array;
	}

	public static  ArrayList<Integer> addArrays( ArrayList<Integer> aaa,  ArrayList<Integer> bbb) throws Exception{
		if (aaa.size()>bbb.size())
			bbb=addZeros(bbb,aaa.size()-bbb.size());
		if (aaa.size()<bbb.size())
			aaa=addZeros(aaa,-aaa.size()+bbb.size());
		ArrayList<Integer> res = new ArrayList<Integer>();
		for(int i=0;i<aaa.size();i++){
			int r=aaa.get(i)+bbb.get(i);
			res.add(r);	
		}
		res=resolveToUnits(res);
		return res;
	}

	public static  ArrayList<Integer> subtractArrays( ArrayList<Integer> aaa,  ArrayList<Integer> bbb) throws Exception{
		if (aaa.size()>bbb.size())
			bbb=addZeros(bbb,aaa.size()-bbb.size());
		if (aaa.size()<bbb.size())
			aaa=addZeros(aaa,-aaa.size()+bbb.size());
		ArrayList<Integer> res = new ArrayList<Integer>();
		for(int i=0;i<aaa.size();i++){
			int r=aaa.get(i)-bbb.get(i);
			res.add(r);	
		}
		res=resolveToUnits(res);
		return res;
	}
	
	private static ArrayList<Integer> addZeros(ArrayList<Integer> array, int n)
	{
		ArrayList<Integer> res = new ArrayList<Integer>();
		for(int i=0;i<n;i++){
			res.add(0);
		}
		res.addAll(array);
		return res;
	}

	private static ArrayList<Integer> resolveToUnits(ArrayList<Integer> list) throws Exception
	{
		ArrayList<Integer> res = new ArrayList<Integer>();
		int resto=0;
		Collections.reverse(list);
		for(int i=0;i<list.size();i++){
			int d=list.get(i)+resto;
			resto=0;
			if(d>9){
				resto = d/10;
				d=d-resto*10;
			}

			if(d<0){
				resto = -1;
				d=d-resto*10;
			}
			res.add(d);
		}
		if(resto>0){
			String restoStr=resto+"";
			for(int i=0;i<restoStr.length();i++){
				res.add(Integer.parseInt(restoStr.substring(restoStr.length()-i-1, restoStr.length()-i)));			
			}
		}
		if(resto<0)
			throw new Exception("IN Subtractions last rest cant be negative");
		Collections.reverse(res);
		return res;
	}

	public static Number multiply(Number aaaN, Number bbbN) throws Exception{


		int signum = aaaN.getSign()*bbbN.getSign();
		int da=aaaN.getDecimalsLenght();
		int db=bbbN.getDecimalsLenght();		
		ArrayList<Integer> aaa = aaaN.getDigitis();
		ArrayList<Integer> bbb = bbbN.getDigitis();

		
		
		if (aaa.size()>bbb.size()){
			ArrayList<Integer> ccc =aaa;
			aaa=bbb;
			bbb=ccc;
		}
		ArrayList<Integer> res = new ArrayList<Integer>();
		for(int i=0;i<bbb.size();i++){
			int index = bbb.size()-1-i;
			ArrayList<Integer> line= mutiplyScalar(aaa,bbb.get(index));
			line=multiply10(line, i);
			res=addArrays(res, line);
		}

		StringBuffer sb = new StringBuffer(res.toString());
		for(int i=3;i<sb.length()-2;i++){
			sb.delete(i-1, i+1);
		}
		sb.insert((sb.length()-1)-(db+da), ".");

		Number n =  new Number("0"+sb.toString().replace("[","").replace("]","").toString());
		n.setSign(signum);
		return n;
	}

	private static ArrayList<Integer> ereaseFrontZero(ArrayList<Integer> a)
	{
		ArrayList<Integer> res = new ArrayList<Integer>();
		if(a!=null && a.size()>0){
			for(int i=0;i<a.size();i++){
				if (a.get(i)==0){
				}
				else
				{
					res.add(a.get(i));
				}
			}
		}
		return res;
	}

	private static ArrayList<Integer> mutiplyScalar(ArrayList<Integer> aaa, Integer scalar) throws Exception
	{
		ArrayList<Integer> res = new ArrayList<Integer>();
		for(int i=0;i<aaa.size();i++){
			int r=aaa.get(i)*scalar;
			res.add(r);	
		}
		res=resolveToUnits(res);
		return res;
	}

	public static Number divideOld(Number aaaN, Number bbbN, int precision) throws Exception
	{
		int signum = aaaN.getSign()*bbbN.getSign();
		int da=aaaN.getDecimalsLenght();
		int db=bbbN.getDecimalsLenght();		
		int ia =aaaN.getIntereLenght();	
		int ib =bbbN.getIntereLenght();
		int decimals =0;

		ArrayList<Integer> res = new ArrayList<Integer>();

		ArrayList<Integer> aaa = aaaN.getDigitis();
		ArrayList<Integer> bbb = bbbN.getDigitis();
		
		if(aaa.size()<bbb.size()){
			decimals = bbb.size()-aaa.size();
			aaa=multiply10(aaa, decimals);
		}
		
		aaa=multiply10(aaa, precision);
		decimals=decimals+precision;
		
		int fitDecimals = aaa.size()% bbb.size();
		if (fitDecimals>0)  {
			aaa=multiply10(aaa, bbb.size()-fitDecimals);
			decimals=decimals+bbb.size()-fitDecimals;
		}
		System.out.println("Decimals:"+decimals);
		ArrayList<Number> tabella = new ArrayList<Number>();
		for(int i=0; i<10 ;i++){
			ArrayList<Integer> t = mutiplyScalar(bbb, i);
			tabella.add(new Number("t"+i,t));
		}
		//List<Integer> step = bbb.subList(0, bbb.size()-aaa.size());
		
		//first step
		List<Integer> step = aaa.subList(0,bbb.size());
		Number s1 = new Number("s1", step);
		int i=0;
		for(i=0;i<10;i++){
			if(isMinor(s1, tabella.get(i))==-1){
				i=(i-1<0?0:i-1);
				res.add(i);
				break;
			}
		}
		
		ArrayList<Integer> s2 = subtractArrays(s1.getDigitis(), tabella.get(i).getDigitis());
		int index = bbb.size();
		for(index=bbb.size(); index<aaa.size();index++){
			s2.add(aaa.get(index));
			Number s2N=new Number("s2",s2);
			int j=0;
			for(j=0;j<10;j++){
				if(isMinor(s2N, tabella.get(j))==-1){
					j=(j-1<0?0:j-1);
					res.add(j);
					break;
				}
			}
			s2 = subtractArrays(s2, tabella.get(j).getDigitis());
			
		}
		
		Number n =  new Number("res",res);
		n.intereLenght=aaa.size()-bbb.size()-decimals+1;
		
		n.setSign(signum);
		return n;
	}
	
	public static Number divide(Number aaaN, Number bbbN, int precision) throws Exception
	{
		int signum = aaaN.getSign()*bbbN.getSign();
		int da=aaaN.getDecimalsLenght();
		int db=bbbN.getDecimalsLenght();		
		int ia =aaaN.getIntereLenght();	
		int ib =bbbN.getIntereLenght();
		int decimals =0;
		
		
		Number n =  new Number();		
		n.setSign(signum);
		return n;
	}
}
